/*
@title: CHIP-8
@author: Milk_Cool
@tags: ['retro']
@addedOn: 2025-08-06
@description: A simple CHIP-8 emulator for Sprig.
*/

/*
WASD are mapped to 5789, K is mapped to 4 and L is mapped to 6.

You can swich between games using J and open the full sized keyboard with I (WASD to navigate, L to toggle)

You can upload your own ROMs by chaning the ROMS array in the main file.
By default, this emulator keeps a pressed key held until its status is queried by the CPU. If you'd like to disable that behavior, change KB_STICKY to false.
*/

// https://github.com/skni-kod/chip-8/blob/main/src/main/java/chip8/Memory.java
const HEX_SPRITES = [
  // 0
  0b11110000,
  0b10010000,
  0b10010000,
  0b10010000,
  0b11110000,
  // 1
  0b00100000,
  0b01100000,
  0b00100000,
  0b00100000,
  0b01110000,
  // 2
  0b11110000,
  0b00010000,
  0b11110000,
  0b10000000,
  0b11110000,
  // 3
  0b11110000,
  0b00010000,
  0b11110000,
  0b00010000,
  0b11110000,
  // 4
  0b10010000,
  0b10010000,
  0b11110000,
  0b00010000,
  0b00010000,
  // 5
  0b11110000,
  0b10000000,
  0b11110000,
  0b00010000,
  0b11110000,
  // 6
  0b11110000,
  0b10000000,
  0b11110000,
  0b10010000,
  0b11110000,
  // 7
  0b11110000,
  0b00010000,
  0b00100000,
  0b01000000,
  0b01000000,
  // 8
  0b11110000,
  0b10010000,
  0b11110000,
  0b10010000,
  0b11110000,
  // 9
  0b11110000,
  0b10010000,
  0b11110000,
  0b00010000,
  0b11110000,
  // A
  0b11110000,
  0b10010000,
  0b11110000,
  0b10010000,
  0b10010000,
  // B
  0b11100000,
  0b10010000,
  0b11100000,
  0b10010000,
  0b11100000,
  // C
  0b11110000,
  0b10000000,
  0b10000000,
  0b10000000,
  0b11110000,
  // D
  0b11100000,
  0b10010000,
  0b10010000,
  0b10010000,
  0b11100000,
  // E
  0b11110000,
  0b10000000,
  0b11110000,
  0b10000000,
  0b11110000,
  // F
  0b11110000,
  0b10000000,
  0b11110000,
  0b10000000,
  0b10000000
];
const ROMS = [
  // Snek https://johnearnest.github.io/chip8Archive/play.html?p=snek
  "A238F76500E0FF073F001206F315E4A16200E5A16201E6A16202E7A1620342007001420171FF420270FF42037101D0114F001206FF0A1200201000010905070880",
  // Slippery Slope https://johnearnest.github.io/chip8Archive/play.html?p=slipperyslope
  "1484000000000000000000000000000000000500000000050005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A030203000000000000000000000000000000008F60A255FF556E008DF080D0A8BA4001127F70FF6F4CFF1E1273FE1EFB65A209FE1EFB557E0C3E48126F80D0A8BA4001129F70FF6F4CFF1E1293FE1EF365A251F355A255FF6500EEF0B0F0B0E0B0B0E0F0E0C0F0F0C0D0F0B0F0B0B0C000C0C0C0C0C0E0D8E8C8C8E0F0D0D0F0B0B0F0F0B0F080F0F0A090F0E030F0F0606060B0B0B060B0F030F02070703020207070705020707060202070F87050203830384820F078B09020E060E09020F8707048010000FFFF0000010302010001000302FF80C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0208800885070080808000008080870008080807070808080000000000000F8F8F8F8F888502050888040201008081020408080605030080830706080508888887058808080788888888870808080807850080808F058000000F808080808F000000000F85088888888588080808088888888888080808080500808080858000000000808080808000000000000E0A3276000613E6201D02FD12F6210D02FD12FA3256100621FD011D0217008304013DCA3266002613D6201D021D121621ED021D1216D006C02A209FD1EF565A34FF01E6B01DCB5A34FF11E6B06DCB5A34FF21E6B0BDCB5A34FF31E6B10DCB5A34FF41E6B15DCB5A34FF51E6B1ADCB57C057D063D4813FCA251F3658900899E899E890479028810888E888E881478016700A336D9858D208C306B036A00A2ED80B0800E800E80B4F01E80D0800E800E80D4700281C0811E811E81C47101D01500EE23C2600E6104A2DDD0147005A2C5D0147004A2C1D0147003A2D5D0147005A2D5D0147005A2B5D0147005A2D9D0147005A2E9D014700560146118A2DDD0147005A2C5D0147004A2D1D0147005A2D5D0147005A2B5D01470056F09EF9E14E66A0163006F05EF9E14F06A0163016F07EF9E14FA6A0163026F08EF9E15046A0163033A011754A204F165710131641516700161003064151E61636063A204F155A2ED80B0800E800E80B4F01E80D0800E800E80D4700281C0811E811E81C47101D01580D080D480D4800480C4A209F01EF06581006F238F174F001578A34FF11E80D0800E800E80D481C0811E811E81C470027101D0158B30A315FB1EFB1EF165850084108D548C443DFF15928D556A003D0C159A8D556A003CFF15A28C456A003C0615AA8C456A0080D0800E800E80D4700281C0811E811E81C4710180938183801140006A0280D080D480D4800480C4A209F01EF0658100310515E48D558C456A00310A15EA6A03310F15F6A31DFB1EF0658B0031141602A321FB1EF0658B0031191658A31DFB1EF0658B00A34FF11E80D0800E800E80D481C0811E811E81C470027101D015611E80D080D480D4800480C4A209F01E8010F055A34FF11E80D0800E800E80D481C0811E811E81C470027101D0156100311E16AEA321FB1EF0658B00A34FF11E80D0800E800E80D481C0811E811E81C470027101D015611980D080D480D4800480C4A209F01E8010F055A34FF11E80D0800E800E80D481C0811E811E81C470027101D01561006F238F174F0016D2A34FF11E80D0800E800E80D481C0811E811E81C470027101D0156A003A011732A30180B0800E800E80B4F01E80D0800E800E80D4700281C0811E811E81C47101D015A33BF71ED985770547146700FF073F0017046F05FF15A30180B0800E800E80B4F01E80D0800E800E80D4700281C0811E811E81C47101D015157AA2ED80B0800E800E80B4F01E80D0800E800E80D4700281C0811E811E81C47101D0154600175E6F06EFA16A033A00177AA33BF71ED985770547146700FF073F00176E6F05FF1514DC3A03178AA202F165A204F155226523C23A0218B800E0360A183C6601600F610CA2E1D0147005A2BDD0147005A2B5D01470057004A2B5D0147005A2CDD0147005A2B1D014700563086414A204F065A206F033F265F129D3457305F229D3457305A205F065A206F033F265F129D3457305F229D3457305803081407004A2DDD0147005A2E1D0147005A2B5D0147005A2D5D0147005A2DDD014700560006100A202F155A204F155A204F16571013164182E700161003064183661636063A204F155186676016011610CA2C5D0147004A2B5D0147005A2E5D0147005A2B5D0147005A2C5D01470047004F629D015A204F165A202F1552265C403A315F41EF41EF1658004C23FC31FA301F41EF41EF41EF41EF41ED235D23582048314D2356F06EFA118B66F07EFA118B66F08EFA118B66F05EFA118B66F09EFA118B6188E23C214DC0505050000000500000000000500000000000000050000050000050000050505050000050000000000050000000505050000000000050500000000000500000005000505050505000A03040100000000000500050000000505000000000505000000050505050000050500000000000505050000000505000000000505000000050505000000000500000000000500000000050505000104050505050505050500000005050500000005050000000005050000140000050000000000050500000005050505000505050500000005050500000005050500000005050505050505090302030000000000000000000000140F0000000000000500000000000000000F000014000000000000000000000000000000000F0000050014000F000000000000000000000000000F00000A030101001400000F000000000000000000000000140F00000000001400000000000F000F00001414140F14000F0F000000001414140000000F0F00000F00140A140F00000F00140F140F140B0000020A0A0A0A0A0A0A000000000A0A000000000A0A000000000A0A000000000A0A005032000A0A005F41000A0A000000000A0A000000000A0A000000000A0A000000000A0A0A0A0A0A0A09030202000000370000000000000005050F0000000000000014004B0000000000000505050000000505050014000514000014000000000F00000000000000000000000000005A32000A0014030008000505050505050505050505050505050005050505050005050500001E0000050500000505050500000505000019000005050500050505050500050505050505050505050505050505090202030F140000140F00000F002300140000000014000F0000000F001E001400002D000000001400140F140000000000001900140014000014000000142300000000000F00140F0014001402020A0100000A0505000000000A050005050000000032001900001446001E00000F46001E1E001446001E1E000F46001900001446001E19000F41001E00001400000500000000000500000000050B01",
  // Cave explorer https://johnearnest.github.io/chip8Archive/play.html?p=caveexplorer
  "1EA52020200020F010700040E090E0909090E090E090E08080D0B0B09090909060609090906090D0B090909050204000000000006090F09090F09090F080E080808080F080E080F0404040F0204080F0202020C060908090609090B07080B09070806010E090A0C0A09090B0B0D0F04040404090907010E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007E000000000000008180000000000001004000000000000200200000000000020020000000000002821000000000000400100000000000040010000000000004001000000000FC04F81000000001030460100000000200820020000000030E82002000000004F04100C0000000050841FF60000000040047709000000004F849790C0000000504C0EE020000000201E0C00100000007FFF0A41320C712D3A1F0A2D2D2D5E52351F1F3D052D2D2D2D2D2D2D2D2D241F00351F672D32101F1A6C2D2D2D2D00000000000000000000000000700000003E00703C8C7F0001C18188428480E002004204428480100400440221048010080044012105001010038801100507101407080090450FD0100C08609009067020180870900901A0201810B054190080281010904C1A07002010D0F0A8324660200F580028B203902920D100243300103009D47217734010144398F8B66302101AA71B9A3361D0300D1E1F8D63C1FAD00FFC0D07E181FF7004600002C0008C200000000080000000000000000000000000FB379873CFBC0000FBF7D8FBEFBE0000C1E6D8DB6C360000F0C7D8DBEF3E0000C1E798DBCC3C0000FBF61EFB6FB60000FB361E736FB60000000000000000000004003D97FFFFFFFFFDFF9D4BFF8B472004005DCBFFD84F2604001D05FFDB5F2104001D85FFDB4720847C1C05FFFFFF2084DA5DC5FF8B472094BCDDE5FF995B20B4E61DE61FBA5B2094C2DDCFE78B4720857DDC3FF9FFFF2FF5839FFFFEFFFF2005FF1FFFFF7FFFFFFCFE1FFFF3BF07FFFCFE1FFFEDDEFBA22DF80C07DDEDFD2AA807F7F9D9E37EAAABFFEC1ED6F3FEA225F80006E71BFEFFFA07F80377C07E000BBFFE037BF8FCFFFB9FFF817CEDF8000BBFFF817F0E00FFFBAFF0617FEE60FFFBBA3FF97FDC98FFFDBDFFFE3FDCF8FFFDBCFFFFCFDCFC0001BD7FFFF1BCFCFFFDBDBF000E3CDC00011DC8FFFF8FDE00001DF3FFFFF3BE000004FBFFFFFC7CF8711F1A2D3541320A2D2D2D2D322D0A1A17103D412D45242D6C35412D0C455E6C322452414D1A5E6C2D322D101A2452351F3A2D5A1F3D0C052D351F671A5E413D415E5E00523D45171045245A2D1F1A6C711F1A2D4117410A5A412D2D6C1F2D0C3249493D45245A2D5E1A243D455A356C322D27455E451F242D1F3A2D322D12710A3217450C052D325E41520A416C2D67323D3D055E1F17411F24412D3D413A6C6C3541450A2D5E631A3D3D2D35410A41052D5A0A1F5E5E00711F1A2D171A5E6C2D2D2D2D0A416C1A0A242D6C1F2D2D2D6735410A412D6C35455E2D2D323D3D2D10415A3224322D0C41320C2D41240C052D241A6C5E005E2467416700E06000610062006301D011F31E720170084040710140406000312016AA00EE610262016300A000F31EF065A202F01ED1257105413E7206413E61027301534016C600EE6080F015F007300016E800EEA5A460046116D0116034611DD01100EE60A36178A6C6F155641326C000EE60A3618BA6C6F155640C26C000EE60A36197A6C6F155640D26C000EE0000000000000000FFFFFFFFFFFFFFFF00FE55228800000000000011918B8FDFC3BDBDBDB9B9BDBD817E5A7E7E5A7E81E7E7C3818242DB93E7E7C3814142DBC904010103010103000201010101010103000201010101010100000202020201060401030103010101020101010101010500010106010102020001020201050000030101010101010401010102020202020501010101010101020501010601010501010101030101030401060101010105020102010101020200010005010100000501030003010104010101030101010201010101010101000202010601050200040101010101010102010106010501050001010201010101000101000105010500030105010101040301010101010102010101050201010005050106000101000303010401050303050501010101050501010101010101010201010601010105A76A00EEA78A00EEA7AA00EEA7CA00EEA7EA00EEA80A00EEA82A00EEA84A00EE80EE800EB86A6800288AF81EF765A3A4F81EF75578083820189200EE800E800E800EA72AF01E00EE6A0069008D108DD68DD68DD68C208CC68CC68CC600EE289000E0610062006300A3A4F31EF065400628B228A6D128730171084140720841406100322018D200EE802E800E800E8014A3A4F01E00EE28F2F06528A6811E811E811E822E822E822ED12800EE8130824000EE29162900291628F28050F0552916290000EE81D082C000EE83D084C000EE292E816400EE292E72FF00EE292E720100EE292E290000EE294028F2F06500EE60004C0000EE2952300560004005600100EE6700680788153F0100EE680388253F0100EE28F2F065400167014004670100EE2934836429167201296C3701199E7401199000EE291628F2F06540046901294C292E28F26001F055294C8D308C40650685A4291C00EE6501293474FF291C00EE650519C42934836474FF29A000EE29408164296C370100EE2940816472FF296C370100EE29C229D029CC00EE2940296C370100EE29408164296C370100EE29D000EE292E8164296C370119DA292E816472FF296C370100EE29C2298C29A029CC00EE295A30001A0E292E8164296C370119F8298C29A000EE66FF4A001A2E29346506291C6A0000EE66014A011A2E29346507291C6A0100EE293A296C370100EE293474FF6501291C298C6505291C00EE66014A0066FF295A30001A64293A28F2F065300500EE2952300100EE293483646501291C293474FF6505291C00EE00A378FD5528C8FB0A4B0428C84B072A444B092A544B062A7C49001AB16020F01800E0AAAAF0657001AAAAF055A378FD6500EE0040000000204000004020000000000060F0F060006060007040F0500102040810204080030607040E06070403030C0407050704070609040A040B030503050305030503000201050A0607070809030C0C0C0C0E03040201090806070A090A0B0D0E0F0D0002010A0D0305060809040B0B0B0B0B00030200010506070504080B0D0D030F28EB0A7A02EF282A6A4A5A427A0A6E2808084A08FF405E52427E40CF084A080800705050D7545454545454D514147C00282828EB0A6A0A3A223A2A2E2038082808081C7F5CC9081C601C08081C601C08100003F3101010DFD01616101010101000024A6670787CFF7C7870664A0200000076525A040039FB3900045A52760000107E087E107E087E107E087E107E10100808D852D010382B3A1202C24E781018105410FF047C407C041C1000286C743800F2EA0202C30AFA02FAA28A22F29A380808083E22FB223E08080808080808080808083E22FB223E0808080808080A080808083E22FB223E080808080A080A080808083E22FB223E08080A080A080A083D0600EE340700EE65FC00EE00E06100640083DE833E833E833EAB61F31EF41EF065850062002C61AADDC00CF01E85563F00AAEDD124720432201C897104740134101C7B6AFF69FFA278FD1EF065300000EE80DEAB01F01EF1658A008910800E800E811E811EAAEDD014AAF5D01400EE80CE800E81BE811EAAF1D01400EE88C087B000EE5CA000EE5B9000EE660100EEAB6180DE800E800E800EF01E630F82C08232F21EF0658100AAF9630782B08232F21EF06580126F0130002CE5300000EE8C808B706F0000EEFD1EF06590D000EE8D002C6D00EE7CFF2CF14F0000EE3CFF00EEAB516C0F1D297C012CF14F0000EE3C1000EE6C00AB311D297BFF2CF14F0000EE3BFF00EE6B07AB211D297B012CF14F0000EE3B0800EE6B00AB411D2942071D3742091D4942051D5B42081D6D00EE600CF05500E060A561A5A6C6F155642426C0F00A00EEABA92D9100EEABAE2D9100EE00E060A561C9A6C6F155642026C0F00A00EE00E060A66115A6C6F155642426C0F00A00EE00E060A66139A6C6F155642426C0F00A00EE00E060A6618AA6C6F155641126C0F00A00EE00E060A6619BA6C6F155640526C0F00A00EE00EE6E002AAB00EE6E012AAB00EE6E022AAB00EE6E032AAB00EE6E042AAB00EE6E052AAB00EE6E062AAB00EE6E072AAB00E060A561E9A6C6F155642C26C0F00AA4A426A01E511E391DB31E151E0F1DAD1DD71E1B1DA71E211E331DE900EE1DC51E271DFB1E2DAAAAF065300700EE7001AAAAF05500E060A6615DA6C6F155642D26C0F00AAB316000F05500EEA278FD1E6001F05580DEBE53A27826A0270026E4270026F0270E26E4270E26F0271C26E4271CA3A426A0F00A6C0A6B042C6D2CD1F20A2CD12CDF66002D7F36002E992E7336002C6D1ECB",
  // Spock Paper Scissors https://johnearnest.github.io/chip8Archive/play.html?p=spockpaperscissors
  "146E387CDEBEBE4C387E467C447CCCFC824428106CAAC638EEAAEE5482FE7E7EDB7E727E3C00000040E040E000E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF001C101C04041C0000E7A5E58585870000392121212139000040408040404000000E0F0F0E08080000337B7B7B7A4A00009EDCDE9E1C1E0000E0F0F0E0B0B000001C224038044830001C2240404044380018240808102020001C224038044830001C224038044830000C122224444830000C12123E284442001C2240380448300000140E5F1F2E4800000000FFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000FEFE00000000F078FEFE78F00000000000000000000000EEAAEC8A00000000EEC886EE00000000E18161E100000000DD55D554000000004243C38200000000BA3223B900000000808080000000000009090F010F0000003C2424243C00000090909090F00000001212120A0600000078407808780000000F0808080F0000003C243C202000000090909090F06400650024B024EA6A096B09A225F433F265F029DAB57A05F129DAB57A05F229DAB56A296B09A225F433F265F029DAB57A05F129DAB57A05F229DAB5257014AA00EEA22E6A006B006D08DAB87A08FD1E4A407B084A406A003B2014B8A32E6A006B006D08DAB87A08FD1E4A407B084A406A003B2014D2F30A00E000EEA22E6A006B006D08DAB87A08FD1E4A207B084A206A003B2014F2A42E6A006B086D08DAB87A08FD1E4A207B084A206A003B10150CA44E6A206B086D08DAB87A08FD1E4A407B084A406A203B101526F30AA42E6A006B086D08DAB87A08FD1E4A207B084A206A003B101542A44E6A206B086D08DAB87A08FD1E4A407B084A406A203B10155C00EE257C259A25C625F625C600EE6600F30A430766014308660243096603430A6604430066054600157C00EECEFF6F008FE73F0067016F328FE73F0067026F658FE73F0067036F988FE73F0067046FCB8FE73F00670500EE6B126A0D460127244602272A46032730460427364605273C6A2D470127244702272A47032730470427364705273C00EE967016603601160E470216CE4703168447041684470516CE3602162247011684470316CE470416CE4705168436031636470116CE4702168447041684470516CE3604164A470116CE47021684470316CE470516843605165E47011684470216CE47031684470416CE00EE6A0F6B03A22BDAB36A2F6B03A22BDAB327186A0F6B03A22BDAB36A2F6B03A22BDAB300EE6A0F6B03A228DAB36A096B09A225F433F265F029DAB57A05F129DAB57A05F229DAB574016A096B09A225F433F265F029DAB57A05F129DAB57A05F229DAB527186A0F6B03A228DAB300EE6A2F6B03A228DAB36A296B09A225F533F265F029DAB57A05F129DAB57A05F229DAB575016A296B09A225F533F265F029DAB57A05F129DAB57A05F229DAB527186A2F6B03A228DAB300EE6C46FC15FC073C00171C00EEA202DAB700EEA209DAB700EEA210DAB700EEA217DAB700EEA21E7AFFDAB700EE",
];

const INTERVAL_TIME = 5;
const TICKS_PER_TIME = 10;
const KB_STICKY = true; // Whether we should hold the keys until the next scan
const KB_DOWN = 30; // For how many cycles we should hold the keys

const screenParts = "01234567";
const emptyBitmap = () => {
  return ("\n" + "0".repeat(16)).repeat(16);
};
let screen;
const clearScreen = () => screen = screenParts.split("").map(x => [x, emptyBitmap()]);
clearScreen();
const flipPixel = (x, y) => {
  x %= 64;
  y %= 32;
  const index = Math.floor(x / 16) + (y >= 16 ? 4 : 0);
  const indexInner = (y & 0xf) * 17 + (x & 0xf) + 1;
  const current = screen[index][1]; // string
  const ret = current[indexInner] === "9";
  screen[index][1] = current.slice(0, indexInner) + (ret ? "0" : "9") + current.slice(indexInner + 1);
  return ret;
};

const render = () => setLegend(...screen);
render();

const screenMap = map`0123
4567`;

const kbMap = map`123c
456d
789e
a0bf`;
let kbOn = false;
let kbN = 5;
const getKbSprite = n => {
  let digit = HEX_SPRITES.slice(n * 5, (n + 1) * 5).map(x => "0".repeat(6) + x.toString(2).padStart(8, "0").replaceAll("1", "9") + "0".repeat(2));
  digit = new Array(5).fill("0".repeat(16))
    .concat(digit)
    .concat(new Array(3).fill("0".repeat(16)))
    .concat(["0".repeat(4) + (n === kbN ? "9" : "0").repeat(8) + "0".repeat(4)])
    .concat(new Array(2).fill("0".repeat(16)));
  if(ctx.kb[n] > 0) {
    digit[0] = "9".repeat(16);
    digit[15] = "9".repeat(16);
    for(let i = 0; i < 16; i++) {
      digit[i] = "9" + digit[i].slice(1, 15) + "9";
    }
  }
  return "\n" + digit.join("\n");
};
const renderKeyboard = () => {
  setLegend(
    ...new Array(16).fill(0).map((x, i) => [i.toString(16), getKbSprite(i)])
  );
};
const moveKb = (xp, yp) => {
  const kb = kbMap.split("\n");
  let x, y;
  for(x = 0; x < 4; x++) {
    let flag = false;
    for(y = 0; y < 4; y++) 
      if(kb[y][x].toLowerCase() === kbN.toString(16).toLowerCase()) {
        flag = true;
        break;
      }
    if(flag) break;
  }
  
  x += xp;
  y += yp;
  while(x < 0) x += 4;
  while(y < 0) y += 4;
  x &= 0b11;
  y &= 0b11;

  kbN = parseInt(kb[y][x], 16);
  renderKeyboard();
};

const toggleKeyboard = () => {
  kbOn = !kbOn;
  if(kbOn) setMap(kbMap);
  else setMap(screenMap);
};

let interval = null;
let timerInterval = null;

const norm8b = n => {
  while(n < 0) n += 0x100;
  return n & 0xff;
}
const norm12b = n => {
  while(n < 0) n += 0x1000;
  return n & 0xfff;
}

const ctx = {
  pc: 0x200,
  reg: new Array(16).fill(0),
  kb: new Array(16).fill(0),
  i: 0x000,
  ram: [],
  stack: [],
  delay: 0,
  sound: 0
};
const init = romIndex => {
  ctx.pc = 0x200;
  ctx.reg = new Array(16).fill(0);
  ctx.i = 0x000;
  ctx.ram = [...HEX_SPRITES].concat(new Array(0x1000 - HEX_SPRITES.length).fill(0));
  ctx.kb = new Array(16).fill(0);
  for(let i = 0; i < ROMS[romIndex].length; i += 2)
    ctx.ram[0x200 + i / 2] = parseInt(ROMS[romIndex].slice(i, i + 2), 16);
  ctx.stack = [];
  
  clearScreen();

  ctx.delay = 0;
  ctx.sound = 0;

  if(interval !== null) clearInterval(interval);
  interval = setInterval(() => {
    for(let i = 0; i < TICKS_PER_TIME; i++)
      tick();
  }, INTERVAL_TIME);
  if(timerInterval !== null) clearInterval(timerInterval);
  timerInterval = setInterval(timerTick, 1000 / 60);
};

const tick = () => {
  if(kbOn) return;
  
  const inst = ctx.ram.slice(ctx.pc, ctx.pc + 2);
  const parts = [inst[0] >> 4, inst[0] & 0xf, inst[1] >> 4, inst[1] & 0xf];
  const addr = (parts[1] << 8) | inst[1];
  let add2 = true;
  
  if(inst[0] === 0x00 && inst[1] === 0xe0) {
    clearScreen();
    render();
  } else if(inst[0] === 0x00 && inst[1] === 0xee)
    ctx.pc = ctx.stack.pop();
  else if(parts[0] === 0x1) {
    ctx.pc = addr;
    add2 = false;
  } else if(parts[0] === 0x2) {
    ctx.stack.push(ctx.pc);
    ctx.pc = addr;
    add2 = false;
  } else if(parts[0] === 0x3) {
    if(ctx.reg[parts[1]] == inst[1])
      ctx.pc += 2;
  } else if(parts[0] === 0x4) {
    if(ctx.reg[parts[1]] != inst[1])
      ctx.pc += 2;
  } else if(parts[0] === 0x5) {
    if(ctx.reg[parts[1]] == ctx.reg[parts[2]])
      ctx.pc += 2;
  } else if(parts[0] === 0x6) 
    ctx.reg[parts[1]] = inst[1];
  else if(parts[0] === 0x7) 
    ctx.reg[parts[1]] += inst[1];
  else if(parts[0] === 0x8 && parts[3] == 0x0) 
    ctx.reg[parts[1]] = ctx.reg[parts[2]];
  else if(parts[0] === 0x8 && parts[3] == 0x1) {
    ctx.reg[parts[1]] |= ctx.reg[parts[2]];
    ctx.reg[0xf] = 0;
  } else if(parts[0] === 0x8 && parts[3] == 0x2) {
    ctx.reg[0xf] = 0;
    ctx.reg[parts[1]] &= ctx.reg[parts[2]];
  } else if(parts[0] === 0x8 && parts[3] == 0x3) {
    ctx.reg[parts[1]] ^= ctx.reg[parts[2]];
    ctx.reg[0xf] = 0;
  } else if(parts[0] === 0x8 && parts[3] == 0x4) {
    const res = ctx.reg[parts[1]] + ctx.reg[parts[2]];
    ctx.reg[parts[1]] = res;
    ctx.reg[0xf] = res > 0xff ? 1 : 0;
  } else if(parts[0] === 0x8 && parts[3] == 0x5) {
    const tmp = ctx.reg[parts[1]] >= ctx.reg[parts[2]] ? 1 : 0;
    ctx.reg[parts[1]] -= ctx.reg[parts[2]];
    ctx.reg[0xf] = tmp;
  } else if(parts[0] === 0x8 && parts[3] == 0x6) {
    const res = ctx.reg[parts[2]] >> 1;
    const tmp = ctx.reg[parts[2]] & 0x1
    ctx.reg[parts[1]] = res;
    ctx.reg[0xf] = tmp;
  } else if(parts[0] === 0x8 && parts[3] == 0x7) {
    const tmp = ctx.reg[parts[2]] >= ctx.reg[parts[1]] ? 1 : 0;
    ctx.reg[parts[1]] = ctx.reg[parts[2]] - ctx.reg[parts[1]];
    ctx.reg[0xf] = tmp;
  } else if(parts[0] === 0x8 && parts[3] == 0xe) {
    const res = ctx.reg[parts[2]] << 1;
    const tmp = (ctx.reg[parts[2]] >> 7) & 0x1;
    ctx.reg[parts[1]] = res;
    ctx.reg[0xf] = tmp;
  } else if(parts[0] === 0x9) {
    if(ctx.reg[parts[1]] != ctx.reg[parts[2]])
      ctx.pc += 2;
  } else if(parts[0] === 0xa)
    ctx.i = addr;
  else if(parts[0] === 0xb)
    ctx.pc = addr + ctx.reg[0x0];
  else if(parts[0] === 0xc)
    ctx.reg[parts[1]] = Math.floor(Math.random() * 256) & inst[1];
  else if(parts[0] === 0xd) {
    let collision = false;
    for(let y = 0; y < parts[3]; y++)
      for(let x = 0; x < 8; x++)
        if((ctx.ram[ctx.i + y] >> (7 - x)) & 1)
          collision = flipPixel(x + ctx.reg[parts[1]], y + ctx.reg[parts[2]]) || collision; // ||= does NOT WORK
    ctx.reg[0xf] = collision ? 1 : 0;
    render();
  } else if(parts[0] === 0xe && inst[1] === 0x9e) {
    if(ctx.kb[ctx.reg[parts[1]]] > 0)
      ctx.pc += 2
    if(KB_STICKY) ctx.kb[ctx.reg[parts[1]]] = 0;
  } else if(parts[0] === 0xe && inst[1] === 0xa1) {
    if(!ctx.kb[ctx.reg[parts[1]]] > 0)
      ctx.pc += 2
    if(KB_STICKY) ctx.kb[ctx.reg[parts[1]]] = 0;
  } else if(parts[0] === 0xf && inst[1] === 0x07) {
    ctx.reg[parts[1]] = ctx.delay;
  } else if(parts[0] === 0xf && inst[1] === 0x0a) {
    const key = ctx.kb.findIndex(x => x > 0);
    if(key === -1) add2 = false;
    else ctx.reg[parts[1]] = key;
    if(KB_STICKY) ctx.kb = ctx.kb.map(() => 0);
  } else if(parts[0] === 0xf && inst[1] === 0x15)
    ctx.delay = ctx.reg[parts[1]];
  else if(parts[0] === 0xf && inst[1] === 0x18)
    ctx.sound = ctx.reg[parts[1]];
  else if(parts[0] === 0xf && inst[1] === 0x1e)
    ctx.i += ctx.reg[parts[1]];
  else if(parts[0] === 0xf && inst[1] === 0x29)
    ctx.i = (ctx.reg[parts[1]] & 0xf) * 5;
  else if(parts[0] === 0xf && inst[1] === 0x33) {
    ctx.ram[ctx.i] = Math.floor(ctx.reg[parts[1]] / 100);
    ctx.ram[ctx.i + 1] = Math.floor(ctx.reg[parts[1]] / 10) % 10;
    ctx.ram[ctx.i + 2] = Math.floor(ctx.reg[parts[1]]) % 10;
  } else if(parts[0] === 0xf && inst[1] === 0x55) {
    for(let i = 0; i <= parts[1]; i++) {
      ctx.ram[ctx.i] = ctx.reg[i];
      ctx.i++
    }
  } else if(parts[0] === 0xf && inst[1] === 0x65) {
    for(let i = 0; i <= parts[1]; i++) {
      ctx.reg[i] = ctx.ram[ctx.i];
      ctx.i++;
    }
  }
  
  if(add2) ctx.pc += 2;
  if(!KB_STICKY) ctx.kb = ctx.kb.map(x => x > 0 ? x - 1 : x);
  ctx.reg = ctx.reg.map(norm8b);
  ctx.ram = ctx.ram.map(norm8b);
  ctx.i = norm12b(ctx.i);
};

const timerTick = () => {
  if(ctx.delay > 0) ctx.delay--;
  if(ctx.sound > 0) {
    playTune(tune`
37.5: C5~37.5,
1162.5`);
    ctx.sound--;
  }
}

let currentGame = 0;

onInput("w", () => {
  if(kbOn) moveKb(0, -1);
  else ctx.kb[5] = KB_DOWN;
});
onInput("a", () => {
  if(kbOn) moveKb(-1, 0);
  else ctx.kb[7] = KB_DOWN;
});
onInput("s", () => {
  if(kbOn) moveKb(0, 1);
  else ctx.kb[8] = KB_DOWN;
});
onInput("d", () => {
  if(kbOn) moveKb(1, 0);
  else ctx.kb[9] = KB_DOWN;
});
onInput("k", () => {
  if(!kbOn) ctx.kb[4] = KB_DOWN;
});
onInput("l", () => {
  if(kbOn) {
    ctx.kb[kbN] = ctx.kb[kbN] > 0 ? 0 : KB_DOWN;
    renderKeyboard();
  } else ctx.kb[6] = KB_DOWN;
});
onInput("i", () => {
  kbOn ? render() : renderKeyboard();
  toggleKeyboard();
});
onInput("j", () => {
  currentGame++;
  currentGame %= ROMS.length;
  init(currentGame);
});
setMap(screenMap);
init(0);
